package core.comunication;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;

import core.proxy.TQueueManagerProxyP2P;
import core.util.COREServiceRegistry;
import core.util.SockUtil;




/**
 * Classe che si occupa del trasferimento file lato server. Il server riceve il file inviato dal FileClient ( PUSH )
 * Essa decide la porta di trasmissione file
 * @author Vincenzo Merlo, Scuderi Giovanni Luca, Paolo Giuffrida
 *
 */
public class FileServer implements Runnable {

	private final Logger logger = COREServiceRegistry.getLogger(TQueueManagerProxyP2P.class.toString()); //istanza logger di Log4j
	private static ConcurrentHashMap<Integer, String> hashFile = new ConcurrentHashMap<Integer, String>(); //HashMap che contiene i chunk
	private final int port; //porta di ricezione file

	/**
	 * Costruttore di default
	 * @param port porta di ascolto
	 */
	public FileServer(int port) {
		this.port = port;
	}

	/**
	 * Metodo Thread che si occupa di ricevere i pacchetti
	 */
	@Override
	public void run() {
		
			
			while (true) {
				try {
					MSGRequestUpload msg = MessageServer.getMessagesUP().take();
					DatagramSocket socket = new DatagramSocket();
					ByteArrayOutputStream output = new ByteArrayOutputStream();
					ObjectOutputStream oOut = new ObjectOutputStream(output);
	
					Integer portFileTransfer = null;
					
					do {
						portFileTransfer = new Integer(SockUtil.startingPort + hashFile.size());
					} while (hashFile.containsKey(portFileTransfer));
					
					logger.debug("Porta di trasferimento " + msg.getAddr() + ":"+ portFileTransfer + " " + msg.getFileName() + " " + msg.toString());
					
					MSGRequestUploadAck ack = new MSGRequestUploadAck();
					ack.setFile(msg.getFileName().hashCode());
					ack.setPort(portFileTransfer);
					
					oOut.writeObject(ack);
					DatagramPacket packet = new DatagramPacket(
							output.toByteArray(),
							output.toByteArray().length,
							msg.getAddr(),
							port);
	
					socket.send(packet);
					socket.close();
					Executors.newSingleThreadExecutor().execute(new TransferHandler(portFileTransfer, msg.getFileName(), msg.getAddr()));
					
					hashFile.put(portFileTransfer,  msg.getFileName());
					
				} catch (InterruptedException e) {
					logger.debug(this.getClass().getCanonicalName() + " " + e.getClass().getSimpleName()
							+ " " + e.getLocalizedMessage());
				}catch (EOFException e) {
					logger.debug(this.getClass().getCanonicalName() + " " + e.getClass().getSimpleName()
							+ " " + e.getLocalizedMessage());
				} catch (SocketTimeoutException e) {
					logger.debug(this.getClass().getCanonicalName() + " " + e.getClass().getSimpleName()
							+ "st " + e.getMessage());
				} catch (SocketException e) {
					logger.debug(this.getClass().getCanonicalName() + " " + e.getClass().getSimpleName()
							+ " " + e.getMessage());
				} catch (FileNotFoundException e) {
					logger.debug(this.getClass().getCanonicalName() + " " + e.getClass().getSimpleName()
							+ " " + e.getMessage());
				} catch (IOException e) {
					logger.debug(this.getClass().getCanonicalName() + " " + e.getClass().getSimpleName()
							+ " " + e.getMessage());
				} 
			}
	}

	/**
	 * ritorna la porta di comunicazione  , ATTENZIONE : è diversa da quella di trasferimento che viene presa runtime
	 * @return porta di comunicazione
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Setta la hashmap relativo al file
	 * @param hashFile
	 */
	public synchronized static void setHashFile(ConcurrentHashMap<Integer, String> hashFile) {
		FileServer.hashFile = hashFile;
	}

	/**
	 * Metodo che ritorna la mappa associata al file in trasferimento
	 * @return hashmap associata al file 
	 */
	public synchronized static ConcurrentHashMap<Integer, String> getHashFile() {
		return hashFile;
	}
}
